<?php

require_once 'js_token_manager.class.php';
require_once 'js_ast.class.php';

class JS_Parser {
	
	private $_head;
	private $_tree;
	private $_level;
	
	public function __construct() {}
	
	public function &parse($str, &$token=null, &$curr=null) {
		if ($str) {
			// set the level
			$this->_level = 0;
			// create the tree
			$this->_tree = new JS_AST();
			// tokenize the string
			$token = JS_TokenManager::tokenize($str);
			// create the root node
			$this->_head = $curr = $this->_tree->getRoot();
		}
		
		_d('IN PARSE ROOT #' . $this->_level . ' > ' . $token);
		
		if (!$token) {
			//_d($this->_head);
			return $this->_tree;
		}
		else if (!$curr) {
			return $curr;
		}
		
		++$this->_level;
		
		$node = null;
		switch ($token->getKind()) {
			case 'FOR':
				$node = $this->_parseFor($token);
				break;
			case 'IF':
				$node = $this->_parseIf($token);
				break;
			case 'LPAREN':
				$node = $this->_parseParens($token);
				break;
			case 'LBRACE':
				$node = $this->_parseBraces($token);
				break;
			case 'STR_LITERAL':
			case 'INT_LITERAL':
				$node = $this->_parseLiteral($op);
				break;
			case 'VAR':
			case 'COMMA':
			case 'SEMICOLON':
			case 'NR':
				// move past the var keyword
				//$token = $token->getNext();
			case 'ID':
			default:
				$node = $this->_parseExpr($token);
				break;				
		}
	
		if ($node) {
			$curr->addChild($node);
		}
		
		$next = $token ? $token->getNext() : null;
		//_d($next . '');
		
		--$this->_level;
		
		return $this->parse(null, $next, $curr);
/*
			$token = $token->getNext();
			
			echo '<pre>';
			//print_r($head);
			echo '</pre>';
		}
		
		if ($node) $head->addChild($node);
		
		echo '<pre>';
		print_r($head);
		echo '</pre>';
		
		return $head;*/
	}
	
	private function &_parseFor(&$token=null) {
		_d('  IN PARSE FOR');
		
		if ($token->getKind('FOR')) $token =& $token->getNext();
		
		$node = new JS_ForNode('for');
		
		$condition = $this->_parseParens($token);
		if ($val = $this->_parseCondition($condition, 'FOR')) {
			$node->addChild($val);
		}
		
		$body = $this->_parseBraces($token);
		if ($val = $this->_parseExpr($body)) {
			$node->addChild($val);
		}
		
		return $node;
	}
	
	private function &_parseIf(&$token=null) {
		_d('  IN PARSE IF');
		
		if ($token->getKind('IF')) $token =& $token->getNext();
		
		$node = new JS_IfNode('if');
		
		$condition = $this->_parseParens($token);
		if ($val = $this->_parseCondition($condition, 'IF')) {
			$node->addChild($val);
		}
		
		$body = $this->_parseBraces($token);
		if ($val = $this->_parseExpr($body)) {
			$node->addChild($val);
		}
		
		return $node;
	}
	
	private function &_parseCondition(&$condition, $type) {
		_d("\tIN PARSE CONDITION");
		
		$node = new JS_ConditionNode();
		switch ($type) {
			case 'FOR':
				// 3 sections
				$tmp = $condition;
				$head = $curr = new JS_Token($tmp->getKind(), $tmp->getValue());
				while ($head) {
					while ($tmp && $tmp->getKind() != 'SEMICOLON') {
						//_d('NODE: ' . $tmp);
						$tmp = $tmp->getNext();
						$curr->setNext($tmp);
						$curr = $tmp;
						
					}
					
					$node->addChild($this->_parseExpr($head));
					if ($tmp) $tmp = $tmp->getNext();
					$head = $curr = $tmp;
				}
				break;
			case 'IF':
				$node->addChild($this->_parseExpr($condition));
				break;
		}

		return $node;
	}
	
	private function &_parseParens(&$token=null) {
		_d("\tIN PARSE PARENTHESIS");
		
		if (!$token) {
			return $token;
		}
		else if ($token->getKind('LPAREN')) {
			$token = $token->getNext();
		}
		
		$start = $token;
		
		$oparen = 1;
		// parse within parens
		while ($oparen != 0 && $token) {
			switch ($token->getKind()) {
				case 'LPAREN':
					++$oparen;
					break;
				case 'RPAREN':
					--$oparen;
					break;
			}
			
			$token = $token->getNext();
		}
		
		if ($token) {
		//	$next = $token->getNext();
			$token->getPrev()->getPrev()->setNext();
		//	$token = $next;
		}
		
		return $start;
	}
	
	private function &_parseBraces(&$token=null) {
		_d("\tIN PARSE BRACES");
		
		if (!$token) {
			return null;
		}

		$start = $token;
		
		$obrace = 1;
		// parse condition - this should be the first open brace
		if ($token->getKind('LBRACE')) {
			$token = $token->getNext();
			while ($obrace != 0 && $token) {
				switch ($token->getKind()) {
					case 'LBRACE':
						++$obrace;
						break;
					case 'RBRACE':
						--$obrace;
						break;
				}
				$token = $token->getNext();
			}
		}
		// no body given
		else if (!$token->getKind('SEMICOLON')) {
			if ($token->getKind('CR')) { $start = $token = $token->getNext(); }
			// treat body as exppression
			while ($token && (!$token->getKind('SEMICOLON') || $token->getKind('CR'))) {
				$token = $token->getNext();
			}
		}
		
		if ($token) {
		//	$next = $token->getNext();
			$token->getPrev()->getPrev()->getPrev()->setNext();
		//	$token = $next;
		}
		
		return $start;
	}
	
	private function &_parseBinOp(&$var, &$op) {
		_d("\tIN PARSE BINOP > " . $var . ', ' . $op);
		
		$node = null; $partner = false;
		switch ($op->getKind()) {
			case 'ASSIGN':
				$node = new JS_AssignmentNode($op->getValue());
				$partner = true;
				break;
			case 'PLUS':
				$node = new JS_AddNode($op->getValue());
				$partner = true;
				break;
			case 'MINUS':
				$node = new JS_MinusNode($op->getValue());
				$partner = true;
				break;
			case 'SLASH':
				$node = new JS_DivideNode($op->getValue());
				$partner = true;
				break;
			case 'STAR':
				$node = new JS_MultiplyNode($op->getValue());
				$partner = true;
				break;
			case 'INCR':
				$node = new JS_IncrementNode($op->getValue());
				break;
			case 'DECR':
				$node = new JS_DecrementNode($op->getValue());
				break;
			case 'EQ':
				$node = new JS_EqualityNode($op->getValue());
				$partner = true;
				break;
			case 'LT':
				$node = new JS_LessThanNode($op->getValue());
				$partner = true;
				break;
			case 'GT':
				$node = new JS_GreaterThanNode($op->getValue());
				$partner = true;
				break;
			case 'REM':
				$node = new JS_RemainderNode($op->getValue());
				$partner = true;
				break;
		}
		
		$node->addChild(new JS_VarNode($var->getValue()));
		
		$op =& $op->getNext();
		if ($partner && ($v = $this->_parseExpr($op))) {
			 $node->addChild($v);
		}
		
		return $node;
	}
	
	private function &_parseExpr(&$token=null) {
		if (!$token) return $token;

		_d("\tIN PARSE EXPRESSION > " . $token);

		$node = null;
		$var = $token;
		if ($var->getKind() == 'ID') {
			$token =& $token->getNext();
			$op =& $token;
		}
		else {
			$op =& $var;
		}
		
		// check
		switch ($op->getKind()) {
			// binary operation
			case 'INCR':
			case 'DECR':
			case 'ASSIGN':
			case 'PLUS':
			case 'MINUS':
			case 'STAR':
			case 'SLASH':
			case 'REM':
			case 'PLUSASSIGN':
			case 'MINUSASSIGN':
			case 'STARASSIGN':
			case 'SLASHASSIGN':
			case 'ANDASSIGN':
			case 'ORASSIGN':
			case 'XORASSIGN':
			case 'REMASSIGN':
			case 'EQ':
			case 'LT':
			case 'GT':
				$node = $this->_parseBinOp($var, $op);
				break;
			// conditional
			case 'S_EQ':
			case 'LE':
			case 'GE':
			case 'NE':
			case 'S_NE':
			case 'SC_OR':
			case 'SC_AND':
			
				break;
			
			// obj property
			case 'DOT':
				
				break;
			case 'RPAREN':
			case 'RBRACE':
			case 'COMMA':
			case 'SEMICOLON':
			case 'CR':
				$node = $this->_parseExpr($token->getNext());
				break;
			case 'NEW':
				$node = $this->_parseObjType($token);
				break;
			/*case 'COMMA':
			case 'SEMICOLON':
			case 'CR':
				/*if ($op !== $var) {
					$node = new JS_VarNode($var->getValue());
				}
				else {
					$token = $token->getNext();
					$node = $this->_parseExpr($token);
				}
				//$token =& $token->getNext();
				$node = $this->_parseExpr($token->getNext());
				break;*/
			case 'STR_LITERAL':
			case 'INT_LITERAL':
				//$node = $this->_parseLiteral($op);
				//break;
			case 'FOR':
			case 'IF':
			case 'LPAREN':
			case 'LBRACE':
				$node = $this->parse(null, $token);
				break;
		}
		
		return $node;
	}
	
	private function &_parseObjType(&$token=null) {
		global $DATA_TYPES;
		_d("\tIN PARSE OBJECT TYPE > " . $token);
		
		if ($token->getKind('NEW')) $token =& $token->getNext();
		
		$node = null;
		if (in_array($token->getValue(), $DATA_TYPES) !== FALSE) {
			// parse param list
			$clazz = 'JS_' . $token->getValue() . 'Node';
			$node = new $clazz(1);
		}
		
		$token =& $token->getNext();
		if ($token->getKind('LPAREN')) $token =& $token->getNext();
		
		_d($token . '');
		
		// get the parameters
		$params = array();
		while (!$token->getKind('RPAREN')) {
			$params[] = $this->parse(null, $token);
			$token = $token->getNext();
		}
		
		switch (count($params)) {
			case 0:
				break;
			case 1:
				$node->setSize($params[0]);
				break;
			default:
			
				break;
		}
		
		return $node;
	}
	
	private function &_parseLiteral(&$token=null) {
		_d("\tIN PARSE LITERAL > " . $token);
		$node = null;
		switch ($token->getKind()) {
			case 'STR_LITERAL':
				$node = new JS_StringNode($token->getValue());
				break;
			case 'INT_LITERAL':
				$node = new JS_NumberNode($token->getValue());
				break;
		}
		
		return $node;
	}
	
	private function _skipTokens($token) {
		$skip = '\n;';
		while ($token && strchr($skip, $token->getValue())) {
			$token = $token->getNext();
		}
		
		return $token ? true : false;
	}
}

?>